package mn.more.foundation.io;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import mn.more.foundation.lang.StringUtil;

/**
 * An utility class regarding <code>java.util.Properties</code>.
 *
 * @author <a href="mailto:mike.liu@aptechmongolia.edu.mn">Mike Liu</a>
 * @version $Id: PropertiesUtil.java 5 2008-09-01 12:08:42Z mikeliucc $
 */
public final class PropertiesUtil {

	private static final int STREAM_BUFFER_SIZE = 4096;

	/** singleton constr. */
	private PropertiesUtil() { }

	/**
	 * return a <code>Properties</code> object based on the
	 * <code>propertiesFile</code>.  This method assumes that the
	 * <code>propertiesFile</code> is located with the <code>contextClass</code>.
	 *
	 * @return java.util.Properties
	 * @throws IOException if unable to load the properties file.
	 */
	public static Properties getProperties(Class contextClass,
	                                       String propertiesFile)
		throws IOException {
		// sanity check
		if (StringUtil.isBlank(propertiesFile)) { return null; }

		// load the properties file as is
		if (contextClass == null) { return getProperties(propertiesFile); }

		return getProperties(contextClass.getResourceAsStream(propertiesFile));
	}

	/**
	 * return a <code>Properties</code> object based on the
	 * <code>propertiesFile</code>.  Loads the properties from the supplied
	 * properties file into a <code>Properties</code> dictionary.  If
	 * <code>propertiesFile</code> was not given with any path, then it is assumed
	 * to be in the classpath
	 *
	 * @return java.util.Properties
	 * @throws IOException if unable to load the properties file.
	 */
	public static Properties getProperties(String propertiesFile)
		throws IOException {
		// sanity check
		if (StringUtil.isBlank(propertiesFile)) { return null; }

		// is this a file on the path
		if (FileUtil.isFileExists(propertiesFile)) {
			FileInputStream in = new FileInputStream(new File(propertiesFile));
			try {
				return getProperties(in);
			} finally {
				in.close();
			}
		}

		// maybe it's in the classpath
		ClassLoader classLoader =
			Thread.currentThread().getContextClassLoader();
		return getProperties(classLoader.getResourceAsStream(propertiesFile));
	}

	/**
	 * create properties object based on <code>in</code>.
	 *
	 * @return <code>java.util.Properties</code>
	 * @see #getProperties(String)
	 */
	public static Properties getProperties(InputStream in) throws IOException {
		// sanity check
		if (in == null) { return null; }

		BufferedInputStream bis = new BufferedInputStream(in,
		                                                  STREAM_BUFFER_SIZE);
		Properties prop = new Properties();

		try {
			prop.load(bis);
		} finally {
			// release stream
			try { bis.close(); } catch (IOException e) { }
		}

		//Enumeration enumeration = prop.keys();
		//while (enumeration.hasMoreElements()) {
		//	String key = (String) enumeration.nextElement();
		//	prop.put(key, SystemProperties.expandVariable(prop.getProperty(key)));
		//}

		return prop;
	}

	public static int getInt(Properties prop, String key, int defValue) {
		if (!check(prop, key)) { return defValue; }
		return Integer.parseInt(prop.getProperty(key));
	}

	public static boolean getBoolean(Properties prop,
	                                 String key,
	                                 boolean defValue) {
		if (!check(prop, key)) { return defValue; }
		return Boolean.parseBoolean(prop.getProperty(key));
	}

	public static String getString(Properties prop,
	                               String key,
	                               String defValue) {
		if (!check(prop, key)) { return defValue; }
		return prop.getProperty(key);
	}

	public static double getDouble(Properties prop,
	                               String key,
	                               double defValue) {
		if (!check(prop, key)) { return defValue; }
		return Double.parseDouble(prop.getProperty(key));
	}

	public static List getList(Properties prop, String key, Class type) {
		ArrayList list = new ArrayList();
		if (!check(prop, key)) { return list; }

		String value = prop.getProperty(key);
		StringTokenizer stz = new StringTokenizer(value, ",");
		try {
			Constructor constructor = type.getConstructor(String.class);
			while (stz.hasMoreTokens()) {
				list.add(constructor.newInstance(stz.nextToken()));
			}
		} catch (Exception e) {
			throw new IllegalArgumentException("The specified type " +
			                                   type.getName() +
			                                   " doesn't support a String-parameterized constructor.");
		}

		return list;
	}

	public static Object getArray(Properties prop,
	                              String key,
	                              Class type,
	                              Class primitiveType) {
		if (!check(prop, key)) { return new Object[0]; }

		String value = prop.getProperty(key);
		StringTokenizer stz = new StringTokenizer(value, ",");
		Object array = Array.newInstance((primitiveType != null ?
		                                  primitiveType :
		                                  type), stz.countTokens());

		try {
			Constructor constructor = type.getConstructor(String.class);
			int index = 0;
			while (stz.hasMoreTokens()) {
				Array.set(array,
				          index++,
				          constructor.newInstance(stz.nextToken()));
			}
		} catch (Exception e) {
			throw new IllegalArgumentException("The specified type " +
			                                   type.getName() +
			                                   " doesn't support a String-parameterized constructor.");
		}

		return array;
	}

	public static Properties selectByPrefix(Properties prop, String prefix) {
		if (prop == null || prop.size() < 1) {
			throw new IllegalArgumentException("Null or empty properties!");
		}
		if (prefix == null || prefix.length() < 1) {
			throw new IllegalArgumentException("Null or empty prefix!");
		}

		Properties newProp = new Properties();
		for (Object objKey : prop.keySet()) {
			String key = objKey.toString();
			if (key.startsWith(prefix)) {
				newProp.setProperty(key.replace(prefix, ""), prop.getProperty(
					key));
			}
		}

		return newProp;
	}

	private static boolean check(Properties prop, String key) {
		if (prop == null || prop.size() < 1) {
			throw new IllegalArgumentException("Null or empty properties!");
		}
		if (key == null || key.length() < 1) {
			throw new IllegalArgumentException("Null or empty key!");
		}
		return prop.containsKey(key);
	}

}
